/*
 * Copyright (c) 2017-2020, Lindenis Tech. Ltd.
 * All rights reserved.
 *
 * File:
 *
 * Description:
 *
 * Author:
 *      xiaoshujun@lindeni.com
 *
 * Create Date:
 *      2020/10/20
 *
 * History:
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

#include "osal_mutex.h"
#include "ffdemux.h"
#include "ffcodec.h"

#if ENABLE_AO
#include "adev_wrapper.h"
#endif

#include "osal_log.h"
#include "osal_common.h"

int g_ao_device = 0; // 0: AUDIO_DEVICE_CODEC, 1: AUDIO_DEVICE_HDMI
int g_ao_rate = 44100;
int g_ao_channel = 1;

#if ENABLE_AO
_handle_t h_ao = 0;
#endif

int g_demux_eof = 0;
int g_audio_end = 0;
int g_video_end = 0;
void * g_priv_as = NULL;
void * g_priv_vs = NULL;
_handle_t h_adec = 0;
_handle_t h_demux = 0;
lock_mutex g_lock;

#define SAVE_PCM 0
#if SAVE_PCM
FILE * g_fd = 0;
char * g_filepath = "/mnt/sdcard/1.pcm";
#endif

static int _ffdemux_msg_cb(void *user, int msg, void * ext1, void * ext2)
{
    switch (msg) {
        case DEMUX_MSG_EOF:
            g_demux_eof = 1;
            logd("message: DEMUX_MSG_EOF");
            break;
        default:
            loge("_ffdemux_msg_cb unknown msg type: %d", msg);
            break;
    }

    return 0;
}

static void * _audio_output_thread(void * arg)
{
    int ret = 0;
    logd("_audio_output_thread running ...");

#if SAVE_PCM
    g_fd = fopen(g_filepath, "wb+");
    if (NULL == g_fd)
    {
        loge("Open %s file error", g_filepath);
        return 0;
    }
#endif

    while(1)
    {
        av_frame2_t * p_frame = NULL;
        ret = ff_adec_receive_frame2(h_adec, &p_frame);
        if (ret == 0) {
#if ENABLE_AO
            //printf("got dec audio data pts: %lld, 0x%08x, size: %d\n", p_frame->pts, p_frame->data[0], p_frame->size[0]);
            adev_out_write(h_ao, p_frame->data[0], p_frame->size[0]);
#endif
            ff_adec_return_frame2(h_adec, p_frame);
#if SAVE_PCM
            if (g_fd)
            {
                fwrite(p_frame->data[0], p_frame->size[0], 1, g_fd);
            }
#endif
        }

        if (ret < 0 && g_audio_end) {
            break;
        }
    }
    logd("_audio_output_thread exit");
#if SAVE_PCM
    if (NULL != g_fd)
    {
        fclose(g_fd);
        g_fd = NULL;
    }
#endif

    return 0;
}

static void * _demux_get_audio_thread(void * arg)
{
    av_frame_t av_frame;
    audio_frame_t * p_aframe;
    int ret = 0;
    logd("_demux_get_audio_thread running ...");

    while (!g_audio_end)
    {
        av_frame.av_index = STREAM_INDEX_AUDIO;
        ret = ffdemux_read_audio(h_demux, &av_frame);
        if (ret < 0)
        {
            if (ret == AVERR_AGAIN) {
                continue;
            } else if (ret == AVERR_EOF) {
                logd("read audio end");
                g_audio_end = 1;
            } else {
                logw("ffdemux_read_audio failed, ret: %d", ret);
            }
            break;
        }
        else
        {
            p_aframe = av_frame.frame.aframe;
            if (p_aframe->size == 0) {
                logd("read audio size: 0");
                g_audio_end = 1;
            }
            //printf("read audio size: %d, pts: %lld\n", p_aframe->size, p_aframe->pts);

            av_frame2_t frame = {0};
            frame.data[0] = p_aframe->data;
            frame.size[0] = p_aframe->size;
            frame.pts = p_aframe->pts;
            ff_adec_send_data2(h_adec, &frame);
        }
    }

    logd("_demux_get_audio_thread exit");

    return 0;
}

static void * _demux_get_video_thread(void * arg)
{
    av_frame_t av_frame;
    video_frame_t * p_vframe;
    int ret = 0;
    logd("_demux_get_video_thread running ...");

    while (!g_video_end)
    {
        av_frame.av_index = STREAM_INDEX_VIDEO;
        ret = ffdemux_read_video(h_demux, &av_frame);
        if (ret < 0)
        {
            if (ret == AVERR_AGAIN) {
                continue;
            } else if (ret == AVERR_EOF) {
                logd("read video end");
                g_video_end = 1;
            } else {
                logw("ffdemux_read_video failed, ret: %d", ret);
            }
            break;
        }
        else
        {
            p_vframe = av_frame.frame.vframe;
            if (p_vframe->vframe.es.es_size[0] == 0) {
                logd("read video size: 0");
                g_video_end = 1;
            }
            //printf("read video size: %d, pts: %lld\n", p_vframe->vframe.es.es_size[0], p_vframe->pts);
        }
    }

    logd("_demux_get_video_thread exit");
    return 0;
}

static void * _demux_get_audio_thread2(void * arg)
{
    av_frame2_t * p_aframe;
    int ret = 0;
    logd("_demux_get_audio_thread2 running ...");

    while (!g_audio_end)
    {
        ret = ffdemux_read_audio2(h_demux, &p_aframe);
        if (ret < 0)
        {
            if (ret == AVERR_AGAIN) {
                continue;
            } else if (ret == AVERR_EOF) {
                logd("read audio end");
                g_audio_end = 1;
            } else {
                logw("ffdemux_read_audio2 failed, ret: %d", ret);
            }
            break;
        }
        else
        {
            if (p_aframe->size == 0) {
                logd("read audio size: 0");
                g_audio_end = 1;
            }
            //printf("read audio size: %d, pts: %lld\n", p_aframe->size[0], p_aframe->pts);

            ff_adec_send_data2(h_adec, p_aframe);
            ffdemux_return_audio2(h_demux, p_aframe);
        }
    }

    logd("_demux_get_audio_thread2 exit");

    return 0;
}

static void * _demux_get_video_thread2(void * arg)
{
    av_frame2_t * p_vframe;
    int ret = 0;
    logd("_demux_get_video_thread2 running ...");

    while (!g_video_end)
    {
        ret = ffdemux_read_video2(h_demux, &p_vframe);
        if (ret < 0)
        {
            if (ret == AVERR_AGAIN) {
                continue;
            } else if (ret == AVERR_EOF) {
                logd("read video end");
                g_video_end = 1;
            } else {
                logw("ffdemux_read_video2 failed, ret: %d", ret);
            }
            break;
        }
        else
        {
            if (p_vframe->size[0] == 0) {
                logd("read video size: 0");
                g_video_end = 1;
            }
            //printf("read video size: %d, pts: %lld\n", p_vframe->size[0], p_vframe->pts);
            ffdemux_return_video2(h_demux, p_vframe);
        }
    }

    logd("_demux_get_video_thread2 exit");
    return 0;
}

static int _init_ffdemux(char * url)
{
    int ret = 0;

    h_demux = ffdemux_create();
    if (!h_demux) {
        loge("ffdemux_create failed");
        return -1;
    }

    ffdemux_config_t config;
    memset(&config, 0, sizeof(config));
    config.cb = _ffdemux_msg_cb;
    config.user = NULL;
    strcpy(config.url, url);
    //config.start_us = 10000000;
    //config.end_us   = 20000000;
    ret = ffdemux_init(h_demux, &config);
    if (ret < 0) {
        loge("ffdemux_init failed");
        goto _failed_;
    }

    if (g_audio_end) {
        ffdemux_ioctrl(h_demux, DEMUX_IOCTRL_DISABLE_AUDIO_STREAM, 0, 0);
    }
    if (g_video_end) {
        ffdemux_ioctrl(h_demux, DEMUX_IOCTRL_DISABLE_VIDEO_STREAM, 0, 0);
    }

    ret = ffdemux_ioctrl(h_demux, DEMUX_IOCTRL_GET_PRIVPARAM_AS, &g_priv_as, 0);
    if (ret < 0 || g_priv_as == NULL) {
        // no audio stream
        g_audio_end = 1;
    }
    ret = ffdemux_ioctrl(h_demux, DEMUX_IOCTRL_GET_PRIVPARAM_VS, &g_priv_vs, 0);
    if (ret < 0 || g_priv_vs == NULL) {
        // no video stream
        g_video_end = 1;
    }
    ffdemux_ioctrl(h_demux, DEMUX_IOCTRL_SET_SPEED_1X, 0, 0);

    return 0;

_failed_:
    ffdemux_destroy(h_demux);
    return -1;
}

static int _init_ffadec()
{
    int ret = 0;

    h_adec = ff_adec_create();
    if (!h_adec) {
        loge("ff_adec_create failed");
        return -1;
    }

    ffadec_config_t adec_cfg = {0};
    adec_cfg.aenc_type = AV_ENC_TYPE_MP3;
    adec_cfg.channels = g_ao_channel;
    adec_cfg.sample_rate = g_ao_rate;
    adec_cfg.bits = 16;
    adec_cfg.p_priv = g_priv_as;
    ret = ff_adec_start(h_adec, &adec_cfg);
    if (ret < 0) {
        loge("ff_adec_start failed");
        goto _failed_;
    }

    return 0;

_failed_:
    ff_adec_destroy(h_adec);
    return -1;
}

#if ENABLE_AO
static int _init_audio_out()
{
    int ret = 0;

    h_ao = adev_out_create();
    if (!h_ao) {
        loge("adev_out_create failed");
        return -1;
    }

    adev_config_t ao_cfg = {0};
    ao_cfg.channels = g_ao_channel;
    ao_cfg.rate = g_ao_rate;
    ao_cfg.bits = 16;
    ao_cfg.period_size = 576;
    ao_cfg.period_count = 2;
    ao_cfg.device = g_ao_device;
    ret = adev_out_open(h_ao, &ao_cfg);
    if (ret < 0) {
        loge("adev_out_open failed");
        goto _failed_;
    }
    return 0;

_failed_:
    adev_out_destroy(h_ao);
    return -1;
}
#endif

static void print_usage_demux(char * this)
{
    printf("\033[32m");
    printf("Usage: \n");
    printf("    %s [media_file] <options>\n", this);
    printf("options can be this:\n");
    printf("    -an             : disable audio stream.\n");
    printf("    -vn             : disable video stream.\n");
    printf("    -l [loop_count] : loop playback for aging test.\n");
    printf("    -d [device]     : audio output device, 0: codec, 1: hdmi, default: 0.\n");
    printf("    -c [channel]    : channels for audio output.\n");
    printf("    -r [rate]       : sample rate for audio output.\n");
    printf("such as: %s /mnt/extsd/1.mp3 -d 0 -c 1 -r 16000 \n", this);
    printf("\033[0m\n");
}

int main(int argc, char **argv)
{
    int ret = 0, i = 0;
    av_frame_t av_frame;
    audio_frame_t * p_aframe;
    video_frame_t * p_vframe;
    pthread_t tid_a = 0, tid_v = 0, tid_ao = 0;
    int test_cnt = 0, cnt = 1;
    int disable_audio = 0;
    int disable_video = 0;

    if (argc < 2)
    {
        print_usage_demux(argv[0]);
        return 0;
    }

    for (i = 1; i < argc;)
    {
        if (!strcmp(argv[i], "-h"))
        {
            print_usage_demux(argv[0]);
            return 0;
        }
        else if (!strcmp(argv[i], "-an") || !strcmp(argv[i], "-disable-audio"))
        {
            disable_audio = 1;
            i += 1;
        }
        else if (!strcmp(argv[i], "-vn") || !strcmp(argv[i], "-disable-video"))
        {
            disable_video = 1;
            i += 1;
        }
        else if (!strcmp(argv[i], "-l"))
        {
            test_cnt = atoi(argv[i+1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-d"))
        {
            g_ao_device = atoi(argv[i+1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-c"))
        {
            g_ao_channel = atoi(argv[i+1]);
            i += 2;
        }
        else if (!strcmp(argv[i], "-r"))
        {
            g_ao_rate = atoi(argv[i+1]);
            i += 2;
        }
        else
        {
            i++;
        }
    }

    init_lock(&g_lock);

    logi("demux url: %s", argv[1]);

_loop_:

    g_demux_eof = 0;
    g_audio_end = disable_audio;
    g_video_end = disable_video;

    ret = _init_ffdemux(argv[1]);
    if (ret < 0) {
        exit(1);
    }

    ret = _init_ffadec();
    if (ret < 0) {
        exit(1);
    }

#if ENABLE_AO
    ret = _init_audio_out();
    if (ret < 0) {
        exit(1);
    }
#endif

    ret = ffdemux_start(h_demux);
    if (ret < 0) {
        loge("ffdemux_start failed");
        exit(1);
    }

    ret = pthread_create(&tid_a, NULL, _demux_get_audio_thread2, NULL);
    if (ret)
    {
        loge("demux pthread_create() failed ");
        exit(1);
    }

    ret = pthread_create(&tid_v, NULL, _demux_get_video_thread2, NULL);
    if (ret)
    {
        loge("demux pthread_create() failed ");
        exit(1);
    }

    ret = pthread_create(&tid_ao, NULL, _audio_output_thread, NULL);
    if (ret)
    {
        loge("_audio_output_thread() failed ");
        exit(1);
    }

    while(g_audio_end != 1 || g_video_end != 1)
    {
        signed char key_val;
        ret = wait_input_char(&key_val, 100000);
        if (ret < 0)
        {
            break;
        }
        else if (ret == 0)
        {
            continue;
        }

        if (key_val > 0 && key_val)
        {
            if (key_val != '\n')
            {
                printf("********* key_val: %c ********** \n", key_val);
            }

            if (key_val == 'q' || key_val == 'Q')
            {
                g_audio_end = 1;
                g_video_end = 1;
                cnt = test_cnt;
                break;
            }
        }
    }

    if (0 != tid_a)
    {
        pthread_join(tid_a, 0);
        tid_a = 0;
    }
    if (0 != tid_v)
    {
        pthread_join(tid_v, 0);
        tid_v = 0;
    }
    if (0 != tid_ao)
    {
        pthread_join(tid_ao, 0);
        tid_ao = 0;
    }

    ffdemux_stop(h_demux);
    ffdemux_deinit(h_demux);
    ffdemux_destroy(h_demux);

    ff_adec_stop(h_adec);
    ff_adec_destroy(h_adec);

#if ENABLE_AO
    adev_out_close(h_ao);
    adev_out_destroy(h_ao);
#endif

    if (cnt < test_cnt) {
        logd("\n\n\t test cnt: %d/%d \n\n", cnt++, test_cnt);
        goto _loop_;
    }

_loopexit_:
    destroy_lock(&g_lock);

    logd("bye bye");

    return 0;
}
